package com.tianji.aigc.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.tianji.aigc.config.SystemPromptConfig;
import com.tianji.aigc.config.ToolResultHolder;
import com.tianji.aigc.constants.Constant;
import com.tianji.aigc.enums.ChatEventTypeEnum;
import com.tianji.aigc.service.ChatService;
import com.tianji.aigc.service.IChatSessionService;
import com.tianji.aigc.vo.ChatEventVO;
import com.tianji.aigc.vo.MessageVO;
import com.tianji.common.utils.UserContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "tj.ai", name = "chat-type", havingValue = "ENHANCE")
public class ChatServiceImpl implements ChatService {

    public static final ChatEventVO STOP_EVENT = ChatEventVO.builder().eventType(ChatEventTypeEnum.STOP.getValue()).build();
    private final ChatClient chatClient;
    private final ChatClient openAiChatClient;
    private final ChatMemory chatMemory;
    private final SystemPromptConfig systemPromptConfig;
    private final StringRedisTemplate stringRedisTemplate;
    private final IChatSessionService iChatSessionService;
    private final VectorStore vectorStore;

    // 存储大模型的生成状态，这里采用ConcurrentHashMap是确保线程安全
    // 通过一个容器，保存当前会话的会话ID 以及 是否继续生成的标识，用于后续停止会话
    // 容器实现：1、使用Map， 2、如果考虑到分布式场景的话，需要使用redis
    // private static final Map<String, Boolean> GENERATE_STATUS = new ConcurrentHashMap<>();
    private static final String GENERATE_STATUS_KEY = "GENERATE_STATUS";

    @Override
    public Flux<ChatEventVO> chat(String question, String sessionId) {
       // var content = chatText(question);
       // log.info("大模型输出内容：{}",content);
        // 获取对话id
        var conversationId = ChatService.getConversationId(sessionId);
        // 大模型输出内容的缓存器，用于在输出中断后的数据存储
        var outputBuilder = new StringBuilder();
        var hashOps = this.stringRedisTemplate.boundHashOps(GENERATE_STATUS_KEY);

        // 生成请求id
        var requestId = IdUtil.simpleUUID();
        // 获取用户id
        var userId = UserContext.getUser();

        // 创建RAG增强
        var qaAdvisor = QuestionAnswerAdvisor.builder(this.vectorStore)
                .searchRequest(SearchRequest.builder()
                        .similarityThreshold(0.6d)
                        .topK(6)
                        .build())
                .build();

        return this.chatClient.prompt()
                .system(promptSystem -> promptSystem
                        .text(this.systemPromptConfig.getChatSystemMessage().get()) //设置系统提示语
                        .param("now",  DateUtil.now())) //设置当前时间的参数
                .advisors(advisor ->advisor
                        // 设置RAG增强
                        .advisors(qaAdvisor)
                        .param(ChatMemory.CONVERSATION_ID, conversationId))
                .toolContext(Map.of(Constant.REQUEST_ID, requestId,Constant.USER_ID,userId)) //通过工具上下文传递参数
                .user(question)
                .stream()
                .chatResponse()
                .doFirst(() -> hashOps.put(sessionId, "true")) // 第一次输出内容时执行
                .doOnError(throwable -> hashOps.delete(sessionId)) // 出现异常时，删除标识
                .doOnComplete(() -> {
                    hashOps.delete(sessionId);
                }) // 完成时执行，删除标识
                .doOnCancel( () ->{
                        // 当输出被取消时，保存输出的内容到历史记录中
                        this.saveStopHistoryRecord(conversationId, outputBuilder.toString());
                    })
                .doFinally(signal ->  getChatTitle(conversationId,sessionId,userId))
                .takeWhile(s -> hashOps.get(sessionId) != null)
                .map(chatResponse -> {
                    // 对于响应结果进行处理，如果是最后一条数据，就把此次消息id放到内存中
                    // 主要用于存储消息数据到 redis中，可以根据消息di获取的请求id，再通过请求id就可以获取到参数列表了
                    // 从而解决，在历史聊天记录中没有外参数的问题
                    var finishReason = chatResponse.getResult().getMetadata().getFinishReason();
                    if (StrUtil.equals(Constant.STOP, finishReason)) {
                        var messageId = chatResponse.getMetadata().getId();
                        ToolResultHolder.put(messageId, Constant.REQUEST_ID, requestId);
                    }
                    // 获取大模型输出的内容
                    String text = chatResponse.getResult().getOutput().getText();
                    // 追加到输出内容中
                    outputBuilder.append(text);
                    // 封装响应对象
                    return ChatEventVO.builder()
                            .eventData(text)
                            .eventType(ChatEventTypeEnum.DATA.getValue())
                            .build();

                })
                .concatWith(Flux.defer(() -> {
                        var result = ToolResultHolder.get(requestId);
                        if(ObjectUtil.isNotEmpty(result)){
                            ToolResultHolder.remove(requestId);
                            // 工具被调用了，需要向前端传递参数
                            return Flux.just(ChatEventVO.builder()
                                    .eventType(ChatEventTypeEnum.PARAM.getValue())
                                    .eventData(result)
                                    .build(),STOP_EVENT);
                        }
                    return Flux.just(STOP_EVENT);
                }));

    }

    @Override
    public void stop(String sessionId) {
        // 移除标记
        var hashOps = this.stringRedisTemplate.boundHashOps(GENERATE_STATUS_KEY);
        hashOps.delete(sessionId);
    }

    @Override
    public String chatText(String question) {
        return this.openAiChatClient.prompt()
                .system(promptSystem -> promptSystem.text(this.systemPromptConfig.getTextSystemMessage().get()))
                .user(question)
                .call()
                .content();
    }

    /**
     * 保存停止输出的记录
     *
     * @param conversationId 会话id
     * @param content        大模型输出的内容
     */
    private void saveStopHistoryRecord(String conversationId, String content) {
        this.chatMemory.add(conversationId, new AssistantMessage(content));
    }

    /**
     * 根据会话内容生成标题
     * @param sessionId
     * @param userId
     */
    public void getChatTitle(String conversationId,String sessionId,Long userId) {

        List<MessageVO> list = iChatSessionService.queryByConversationId(conversationId);
        // 使用StringBuilder拼接消息内容
        StringBuilder userPrompt = new StringBuilder();
        for (MessageVO msg : list) {
            userPrompt.append(msg.getContent()).append("\n");
        }
        if(StringUtil.isEmpty(userPrompt.toString())){
        }else{
            String title = chatClient
                    //设置系统提示语请根据对话内容，提炼 20 字以内能概括核心主题的标题，仅返回标题。
                    .prompt().system(promptSystem -> promptSystem.text(this.systemPromptConfig.getTitleSystemMessage().get()))
                    .user(userPrompt.toString())
                    //.advisors(a-> a.param(ChatMemory.CONVERSATION_ID,sessionId))
                    .call()
                    .content();
            this.iChatSessionService.update(sessionId, title, userId);
        }
    }

}
